package behaviorlearning;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;

import util.XMLWriter;


public class WargusEpisodicCaseLearner {
	public static void main(String args[]) {		
		try	{
			System.out.println(args[0]);
			BufferedReader in = new BufferedReader(new FileReader(args[0]));
			XMLWriter w_behaviors = new XMLWriter(new PrintWriter(new FileWriter(args[1])));
			XMLWriter w_episodes = new XMLWriter(new PrintWriter(new FileWriter(args[2])));
			
			BufferedReader in_goals = null;
			if (args.length >= 4) {
				in_goals = new BufferedReader(new FileReader(args[3]));
			}

			learn_Wargus_cases(in, w_behaviors, w_episodes, in_goals, args[0]);

			if (in_goals != null) in_goals.close();
			in.close();
			w_behaviors.close();
			w_episodes.close();
		} catch(Exception e) {
			if (args.length < 3) {
				System.out.println("Usage: WargusEpisodicCaseLearner <in-log-file> <out-xml-behavior-file> <out-xml-episodes-file> [<goal-annotation-file>]");
			} else {
				System.out.println("Exception:" + e);
				e.printStackTrace();
			} // if 
		}
	}
	
	static void learn_Wargus_cases(BufferedReader in, XMLWriter w_behaviors, 
			XMLWriter w_episodes, BufferedReader in_goal, String id) throws Exception {
		List<Case> cases = WargusTraceLoader.load_Wargus_trace(in, in_goal);
		List<Case> inferred_cases = infer_Wargus_cases(cases);

		String uniqueID = id + "-" + System.currentTimeMillis();
		
		w_behaviors.rawXML("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
		w_behaviors.tag("behaviorlibrary xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
			"  xsi:noNamespaceSchemaLocation=\"Wargus-behaviorbase.xsd\""); 
		{
			for(Case c : inferred_cases) c.writeBehaviorXML(w_behaviors, uniqueID);
		}
		w_behaviors.tag("/behaviorlibrary");
		
		w_episodes.rawXML("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
		w_episodes.tag("episodelibrary xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
			"  xsi:noNamespaceSchemaLocation=\"Wargus-episodebase.xsd\""); 
		{
			for(Case c : inferred_cases) c.writeEpisodeXML(w_episodes, uniqueID);
		}
		w_episodes.tag("/episodelibrary");
		
	}
	
	static List<Case> infer_Wargus_cases(List<Case> cases) {
		LinkedList<Case> inferred_cases;
		List<WargusGoalTree> trees = new LinkedList<WargusGoalTree>();
	
		// First run: classify according to their top goal:
		{
			List<String> goal_l;
			String goal;

			for(Case c:cases) {
				WargusGoalTree found=null;

				goal_l = c.m_goals;
				goal=goal_l.get(0);

				for(WargusGoalTree t:trees) {
					if (t.m_goal.equals(goal)) found = t;
				}

				if (found!=null) {
					found.m_cases.add(c);
				} else {
					WargusGoalTree tree=new WargusGoalTree();
					tree.m_goal=goal;
					tree.m_cases.add(c);

					trees.add(tree);
				} // if 						
			} // for
		}
		
		// Recursively do the same for each node in the tree:
		for(WargusGoalTree t:trees) build_goal_tree(t,1);

		for(WargusGoalTree t:trees) {
			System.out.println("Tree Node:\n" + t.toString());
		}
		
		
		{
			inferred_cases = new LinkedList<Case>();
			
			for(WargusGoalTree t:trees) {
				generate_cases_from_goal_tree(t,inferred_cases);				
			} // for
		}		
		
		return inferred_cases;
	}
	
	static void generate_cases_from_goal_tree(WargusGoalTree tree,LinkedList<Case> cases) {	
		// Generate case for current node:
		Case first_case;
		Case generated_case;
		String goal;
		CaseSolution solution;

		first_case=tree.m_cases.get(0);

		generated_case = new Case(first_case);
		generated_case.m_ID = cases.size();
		
		goal=tree.m_goal;
		
		// Generate plan:
		{
			LinkedList<CaseSolution> subplans = new LinkedList<CaseSolution>();
			CaseSolution subplan = null;

			// Get all the subplans: 
			{
				for(Case c:tree.m_cases) {
					boolean found=false;

					// Check if it's inside any of the subgoals:
					for(WargusGoalTree t:tree.m_subgoals) {
						if (t.m_cases.contains(c)) found = true;
					}
					
					if (!found) {
						// Add the case to the subplans:
						subplan=new CaseSolution();
						subplan.m_type=CaseSolution.BASICOPERATOR;
						subplan.m_action = c.m_solution.m_action;
						subplan.m_cases.add(c);
						subplans.add(subplan);
					} // if 
				} // while 
					
				for(WargusGoalTree g:tree.m_subgoals) {
					// Add the subgoal to the subplans:
					subplan=new CaseSolution();
					subplan.m_type=CaseSolution.SUBGOAL;						
					subplan.m_goal=g.m_goal;
					for(Case c:g.m_cases) subplan.m_cases.add(c);						
					subplans.add(subplan);
				} // while 
			}

			{
				// Add the subplans one by one to the plan:
				CaseSolution plan=null;

				while(subplans.size()>0) {
					subplan=subplans.removeFirst();

					if (plan==null) {
						plan=subplan;
					} else {
						plan=combine_plans(plan,subplan,tree.m_cases);							
					} // if 
				} // while 

				// Generate the FeatureTerm solution:
				solution = plan;
			}
		}

		generated_case.m_goals.clear();
		generated_case.m_goals.add(goal);
		generated_case.m_solution = solution;
		
		cases.add(generated_case);

		// Generate case for subnodes:
		for(WargusGoalTree t:tree.m_subgoals) {
			generate_cases_from_goal_tree(t , cases);				
		} // for
	}
	
	static void build_goal_tree(WargusGoalTree tree,int depth)
	{
		// Classify according to their top goal:
		for(Case c : tree.m_cases) {
			WargusGoalTree found = null;
			String goal = null;
			
			if (c.m_goals.size() > depth) goal = c.m_goals.get(depth);

			if (goal != null) {
				for(WargusGoalTree t:tree.m_subgoals) {
					if (t.m_goal.equals(goal)) {
						found = t;
						break;
					}
				}

				if (found != null) {
					found.m_cases.add(c);
				} else {
					WargusGoalTree t2=new WargusGoalTree();
					t2.m_goal=goal;
					t2.m_cases.add(c);
					tree.m_subgoals.add(t2);
				} // if 
			} // if 
			
		} // for 

		// Recursively do the same for each node in the tree:
		for(WargusGoalTree t:tree.m_subgoals) build_goal_tree(t,depth+1);
	}  
	
	static CaseSolution combine_plans(CaseSolution p1,CaseSolution p2,List<Case> cases)
	{
//		System.out.println("Combining plan with subplan");
		// check whether p1 and p2 can be combined using a simple sequence:
		{
			if (p1_before_p2_p(p1,p2,cases)) {
				// p1 before p2:
				if (p1.m_type==CaseSolution.SEQUENTIAL) {
					p1.m_subsolutions.add(p2);

					for(Case c:p2.m_cases) p1.m_cases.add(c);					
					return p1;
				} // if 
				if (p2.m_type==CaseSolution.SEQUENTIAL) {
					p2.m_subsolutions.add(0,p1);

					for(Case c:p1.m_cases) p2.m_cases.add(c);					
					return p2;
				} // if 

				CaseSolution p=new CaseSolution();
				p.m_type=CaseSolution.SEQUENTIAL;
				p.m_subsolutions.add(p1);
				p.m_subsolutions.add(p2);

				for(Case c:p1.m_cases) p.m_cases.add(c);					
				for(Case c:p2.m_cases) p.m_cases.add(c);					
				return p;
			} else if (p1_before_p2_p(p2,p1,cases)) {
				// p2 before p1:
				if (p1.m_type==CaseSolution.SEQUENTIAL) {
					p1.m_subsolutions.add(0,p2);

					for(Case c:p2.m_cases) p1.m_cases.add(c);					
					return p1;
				} // if 
				if (p2.m_type==CaseSolution.SEQUENTIAL) {
					p2.m_subsolutions.add(p1);

					for(Case c:p1.m_cases) p2.m_cases.add(c);					
					return p2;
				} // if 

				CaseSolution p=new CaseSolution();
				p.m_type=CaseSolution.SEQUENTIAL;
				p.m_subsolutions.add(p2);
				p.m_subsolutions.add(p1);

				for(Case c:p2.m_cases) p.m_cases.add(c);					
				for(Case c:p1.m_cases) p.m_cases.add(c);					
				return p;
			} // if 
		}

		if (p1.m_type==CaseSolution.SEQUENTIAL) {
			CaseSolution p1_prea,p1_a,p1_b;

			p1_prea=null;
			p1_a=null;
			
			for(CaseSolution tmp_p1_b:p1.m_subsolutions) {
				p1_b = tmp_p1_b;
				if (p1_a!=null && p1_b!=null) {
					if (p1_before_p2_p(p1_a,p2,cases) &&
						p1_before_p2_p(p2,p1_b,cases)) {

						p1.m_subsolutions.add(p1.m_subsolutions.indexOf(p1_a)+1,p2);

						for(Case c:p2.m_cases) p1.m_cases.add(c);					
						return p1;					
					} // if 
				} // if 
				if ((p1_prea==null || p1_before_p2_p(p1_prea,p2,cases)) &&
					(p1_b==null || p1_before_p2_p(p2,p1_b,cases))) {

					// p2 should go in parallel with p1_a:
					CaseSolution p=new CaseSolution();
					p.m_type=CaseSolution.PARALLEL;
					p.m_subsolutions.add(p1_a);
					p.m_subsolutions.add(p2);

					for(Case c:p1.m_cases) p.m_cases.add(c);					
					for(Case c:p2.m_cases) p.m_cases.add(c);					

					p1.m_subsolutions.remove(p1_a);
					p1.m_subsolutions.add(p1.m_subsolutions.indexOf(p1_prea)+1,p);

					for(Case c:p2.m_cases) p1.m_cases.add(c);					
					
					return p1;					
				} // if 
				p1_prea=p1_a;
				p1_a=p1_b;
			} // for
			
			p1_b=null;
			
			if ((p1_prea==null || p1_before_p2_p(p1_prea,p2,cases)) &&
				(p1_b==null || p1_before_p2_p(p2,p1_b,cases))) {

				// p2 should go in parallel with p1_a:
				CaseSolution p=new CaseSolution();
				p.m_type=CaseSolution.PARALLEL;
				p.m_subsolutions.add(p1_a);
				p.m_subsolutions.add(p2);

				for(Case c:p1.m_cases) p.m_cases.add(c);					
				for(Case c:p2.m_cases) p.m_cases.add(c);					

				p1.m_subsolutions.remove(p1_a);
				p1.m_subsolutions.add(p1.m_subsolutions.indexOf(p1_prea)+1,p);

				for(Case c:p2.m_cases) p1.m_cases.add(c);					
				
				return p1;					
			} // if 

		} // if 

		if (p2.m_type==CaseSolution.SEQUENTIAL) {
			CaseSolution p2_prea,p2_a,p2_b;

			p2_prea=null;
			p2_a=null;
			for(CaseSolution tmp_p2_b:p2.m_subsolutions) {
				p2_b = tmp_p2_b;
				if (p2_a!=null && p2_b!=null) {
					if (p1_before_p2_p(p2_a,p1,cases) &&
						p1_before_p2_p(p1,p2_b,cases)) {

						p2.m_subsolutions.add( p2.m_subsolutions.indexOf(p2_a)+1 ,p1);

						for(Case c:p1.m_cases) p2.m_cases.add(c);						
						return p2;
					} // if 
				} // if 
				if ((p2_prea==null || p1_before_p2_p(p2_prea,p1,cases)) &&
					(p2_b==null || p1_before_p2_p(p1,p2_b,cases))) {

					// p2 should go in parallel with p1_a:
					CaseSolution p=new CaseSolution();
					p.m_type=CaseSolution.PARALLEL;
					p.m_subsolutions.add(p2_a);
					p.m_subsolutions.add(p1);

					for(Case c:p2.m_cases) p.m_cases.add(c);
					for(Case c:p1.m_cases) p.m_cases.add(c);
					
					p2.m_subsolutions.remove(p2_a);
					p2.m_subsolutions.add(p2.m_subsolutions.indexOf(p2_prea)+1,p);

					for(Case c:p1.m_cases) p2.m_cases.add(c);
					
					return p2;
				} // if 
				p2_prea=p2_a;
				p2_a=p2_b;		
			} // while 
			p2_b = null;
			if ((p2_prea==null || p1_before_p2_p(p2_prea,p1,cases)) &&
				(p2_b==null || p1_before_p2_p(p1,p2_b,cases))) {

				// p2 should go in parallel with p1_a:
				CaseSolution p=new CaseSolution();
				p.m_type=CaseSolution.PARALLEL;
				p.m_subsolutions.add(p2_a);
				p.m_subsolutions.add(p1);

				for(Case c:p2.m_cases) p.m_cases.add(c);
				for(Case c:p1.m_cases) p.m_cases.add(c);
				
				p2.m_subsolutions.remove(p2_a);
				p2.m_subsolutions.add(p2.m_subsolutions.indexOf(p2_prea)+1,p);

				for(Case c:p1.m_cases) p2.m_cases.add(c);
				
				return p2;
			} // if 
		} // if 

		if (p1.m_type==CaseSolution.PARALLEL) {
			p1.m_subsolutions.add(p2);
			for(Case c:p2.m_cases) p1.m_cases.add(c);			
			return p1;
		} // if 

		if (p2.m_type==CaseSolution.PARALLEL) {
			p2.m_subsolutions.add(p1);
			for(Case c:p1.m_cases) p2.m_cases.add(c);			
			return p2;
		} // if 
		
		CaseSolution p = new CaseSolution();
		p.m_type = CaseSolution.PARALLEL;
		p.m_subsolutions.add(p1);
		p.m_subsolutions.add(p2);
		
		return p;
	}
	
	static boolean p1_before_p2_p(CaseSolution p1,CaseSolution p2,List<Case> cases)
	{
		int pos;
		int p1_min=-1,p1_max=-1;
		int p2_min=-1,p2_max=-1;

		for(Case c:p1.m_cases) {
			pos = cases.indexOf(c);
			if (p1_min==-1 || pos<p1_min) p1_min=pos;
			if (p1_max==-1 || pos>p1_max) p1_max=pos;
		} // while 

		for(Case c:p2.m_cases) {
			pos=cases.indexOf(c);

			if (p2_min==-1 || pos<p2_min) p2_min=pos;
			if (p2_max==-1 || pos>p2_max) p2_max=pos;
		} // while 
		
//		System.out.println("p1:(" + p1_min + "-" + p1_max + "), p2:(" + p2_min + "-" + p2_max + ")");

		if (p1_max<p2_min) return true;
		return false;
	}  
	

}
